#include "socket_client.hpp"

// C Library
#include <cstring>
#include <cerrno>		// Display error description

#ifdef _WIN32
#include <string>

std::string strwinerr (int dw);
#endif

/** Timeout for reading data from TCP/IP interface. */
#define TIMEOUT_SEC 5

tcp_client::tcp_client (char serv_ip[], unsigned serv_port)
{

  /** IP address is converted from numbers-and-dots notation into binary
  * format in @c sockaddr_in structure. The default IP address is @c 169.254.1.1
  */
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons (serv_port);
  serv_addr.sin_addr.s_addr = inet_addr (serv_ip);

#ifdef _WIN32
  WORD wVersionRequested;
  WSADATA wsaData;

  /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
  wVersionRequested = MAKEWORD (2, 2);

  ret_val = WSAStartup (wVersionRequested, &wsaData);
  chk_error ();

  /** Confirm that the WinSock DLL supports 2.2.
  * Note that if the DLL supports versions greater
  * than 2.2 in addition to 2.2, it will still return
  * 2.2 in wVersion since that is the version we
  * requested.
  */

  if (LOBYTE (wsaData.wVersion) != 2 || HIBYTE (wsaData.wVersion) != 2)
    {
      /* Tell the user that we could not find a usable */
      /* WinSock DLL.                                  */
      throw ("Could not find a usable version of Winsock.dll");
      //WSACleanup ();
    }

#endif

}

tcp_client::~tcp_client ()
{

// Disconnect when the application is closed
  tcp_disconnect ();
#ifdef _WIN32
  WSACleanup ();
#endif
}

void
tcp_client::tcp_connect ()
{
  /** A TCP socket is created.
  * If socket cannot be created, an exception is thrown
  */
  ret_val = tcp_socket = socket (PF_INET, SOCK_STREAM, 0);

  try
  {
    chk_error ();
  }
  catch ( ...)
  {
    throw;			//Propagate exception to main()
    return;
  }

  /** The program then connects to the defined address. If connection
  * failed, an exception is thrown
  */
  ret_val =
    connect (tcp_socket, (struct sockaddr *) &serv_addr, sizeof (serv_addr));

  try
  {
    chk_error ();
  }
  catch ( ...)
  {
    throw;			//Propagate exception to main()
    return;
  }

  /** Receive time out is set by variable recv_timeout */
  struct timeval recv_timeout;
  recv_timeout.tv_sec = TIMEOUT_SEC;
  recv_timeout.tv_usec = 0;

  ret_val =
    setsockopt (tcp_socket, SOL_SOCKET, SO_RCVTIMEO,
		(const char *) &recv_timeout, sizeof (recv_timeout));

  try
  {
    chk_error ();
  }
  catch ( ...)
  {
    throw;
    return;
  }
}

void
tcp_client::tcp_disconnect ()
{
  closesocket (tcp_socket);
}

void
tcp_client::tcp_send (char send_code[])
{
  ret_val =
    send (tcp_socket, send_code, std::strlen (send_code), MSG_DONTROUTE);

  try
  {
    chk_error ();
  }
  catch ( ...)
  {
    throw;			//Propagate exception to main()
    return;
  }
}

void
tcp_client::tcp_read (char buffer[], unsigned long count)
{
  unsigned long accum_count = 0;

  do
    {
      ret_val =
	recv (tcp_socket, buffer + accum_count, count - accum_count, 0);

      try
      {
	chk_error ();
      }
      catch ( ...)
      {
	throw;			//Propagate exception to main()
      }
      accum_count += ret_val;
    }
  while (accum_count < count);
}


void
tcp_client::chk_error ()
{
//Return if no error
  if (ret_val != -1)
    return;

  throw (strwinerr (WSAGetLastError ()));
}


#ifdef _WIN32

/**
* @param dw socket error code
* @return error message
*/
std::string
strwinerr (int dw)
{
  switch (dw)
    {
    case WSANOTINITIALISED:
      return
	"A successful WSAStartup call must occur before using this function. ";
      break;
    case WSAENETDOWN:
      return "The network subsystem has failed. ";
      break;
    case WSAEACCES:
      return
	"The requested address is a broadcast address, but the appropriate flag was not set. Call setsockopt with the SO_BROADCAST parameter to allow the use of the broadcast address. ";
      break;
    case WSAEINVAL:
      return
	"An unknown flag was specified, or MSG_OOB was specified for a socket with SO_OOBINLINE enabled. ";
      break;
    case WSAEINTR:
      return
	"A blocking Windows Sockets 1.1 call was canceled through WSACancelBlockingCall. ";
      break;
    case WSAEINPROGRESS:
      return
	"A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function. ";
      break;
    case WSAEFAULT:
      return
	"The buf or to parameters are not part of the user address space, or the tolen parameter is too small. ";
      break;
    case WSAENETRESET:
      return
	"The connection has been broken due to keep-alive activity detecting a failure while the operation was in progress. ";
      break;
    case WSAENOBUFS:
      return "No buffer space is available. ";
      break;
    case WSAENOTCONN:
      return
	"The socket is not connected (connection-oriented sockets only). ";
      break;
    case WSAENOTSOCK:
      return "The descriptor is not a socket. ";
      break;
    case WSAEOPNOTSUPP:
      return
	"MSG_OOB was specified, but the socket is not stream-style such as type SOCK_STREAM, OOB data is not supported in the communication domain associated with this socket, or the socket is unidirectional and supports only receive operations. ";
      break;
    case WSAESHUTDOWN:
      return
	"The socket has been shut down; it is not possible to sendto on a socket after shutdown has been invoked with how set to SD_SEND or SD_BOTH. ";
      break;
    case WSAEWOULDBLOCK:
      return
	"The socket is marked as nonblocking and the requested operation would block. ";
      break;
    case WSAEMSGSIZE:
      return
	"The socket is message oriented, and the message is larger than the maximum supported by the underlying transport. ";
      break;
    case WSAEHOSTUNREACH:
      return
	"The remote host cannot be reached from this host at this time. ";
      break;
    case WSAECONNABORTED:
      return
	"The virtual circuit was terminated due to a time-out or other failure. The application should close the socket as it is no longer usable. ";
      break;
    case WSAECONNRESET:
      return
	"The virtual circuit was reset by the remote side executing a hard or abortive close. For UPD sockets, the remote host was unable to deliver a previously sent UDP datagram and responded with a \"Port Unreachable\" ICMP packet. The application should close the socket as it is no longer usable. ";
      break;
    case WSAEADDRNOTAVAIL:
      return
	"The remote address is not a valid address, for example, ADDR_ANY. ";
      break;
    case WSAEAFNOSUPPORT:
      return
	"Addresses in the specified family cannot be used with this socket. ";
      break;
    case WSAEDESTADDRREQ:
      return "A destination address is required. ";
      break;
    case WSAENETUNREACH:
      return "The network cannot be reached from this host at this time. ";
      break;
    case WSAETIMEDOUT:
      return
	"The connection has been dropped, because of a network failure or because the system on the other end went down without notice. ";
      break;
    default:
      return "Unknown socket error. ";
      break;
    }
}
#endif
